package ink.metoo.gude.module.security.service.impl

import ink.metoo.gude.handler.ApiException
import ink.metoo.gude.model.ApiStatus
import ink.metoo.gude.module.mail.service.MailService
import ink.metoo.gude.module.security.domain.AuthVerificationCodeConst.REGISTER_TYPE
import ink.metoo.gude.module.security.domain.AuthVerificationCodeConst.REST_PASSWORD_TYPE
import ink.metoo.gude.module.security.domain.RoleConst
import ink.metoo.gude.module.security.domain.ddl.User
import ink.metoo.gude.module.security.domain.dto.LoginDTO
import ink.metoo.gude.module.security.domain.dto.RegisterDTO
import ink.metoo.gude.module.security.domain.dto.RestPasswordDTO
import ink.metoo.gude.module.security.domain.vo.LoginVO
import ink.metoo.gude.module.security.repository.RoleRepository
import ink.metoo.gude.module.security.repository.UserRepository
import ink.metoo.gude.module.security.service.AuthService
import ink.metoo.gude.module.security.service.TokenService
import ink.metoo.gude.module.security.service.VerificationCodeService
import ink.metoo.gude.util.GravatarUtils
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.thymeleaf.TemplateEngine
import org.thymeleaf.context.Context
import java.util.*

@Service
class AuthServiceImpl(
    private val userRepository: UserRepository,
    private val roleRepository: RoleRepository,
    private val passwordEncoder: PasswordEncoder,
    private val mailService: MailService,
    private val tokenService: TokenService,
    private val verificationCodeService: VerificationCodeService,
    private val templateEngine: TemplateEngine
) : AuthService {

    @Transactional
    override fun register(register: RegisterDTO) {
        if (!checkEmail(register.email)) {
            throw ApiException(ApiStatus.U_2006)
        }
        if (userRepository.existsByAccount(register.account)) {
            throw ApiException(ApiStatus.U_2010)
        }

        verificationCodeService.tryVerificationCode(register.email, REGISTER_TYPE) { code ->
            // 验证码错误
            if (code?.uppercase() != register.verificationCode.uppercase()) {
                throw ApiException(ApiStatus.U_2005)
            }
            val user = User()
            user.email = register.email
            user.nickname = register.nickname
            user.account = register.account
            user.password = passwordEncoder.encode(register.password)
            user.roles = listOf(roleRepository.findByName(RoleConst.ROLE_USER)!!)
            user.avatar = GravatarUtils.generateUrl(register.email)
            userRepository.save(user)
        }
    }

    override fun checkEmail(email: String): Boolean = !userRepository.existsByEmail(email)

    @Transactional
    override fun sendVerificationCode(email: String, type: String, subject: String): Boolean {
        val verificationCode = verificationCodeService.restVerificationCode(email, type)
        val context = Context()
        context.setVariable("username", email)
        context.setVariable("verificationCode", verificationCode)
        val htmlContent = templateEngine.process(
            "mail/verification-code",
            context
        )
        return mailService.send(
            subject = subject,
            to = arrayOf(email),
            content = htmlContent,
            html = true
        )
    }

    override fun login(login: LoginDTO): LoginVO {
        val user = userRepository.findByEmailOrAccount(login.account) ?: throw ApiException(ApiStatus.U_2003)
        if (!passwordEncoder.matches(login.password, user.password)) {
            throw ApiException(ApiStatus.U_2007)
        }
        return LoginVO(
            token = tokenService.createToken(user),
            validity = tokenService.jwtProperties().expirationTime.toMillis()
        )
    }

    override fun restPassword(restPassword: RestPasswordDTO) {
        val user = userRepository.findByEmail(restPassword.email) ?: throw ApiException(ApiStatus.U_2003)

        verificationCodeService.tryVerificationCode(restPassword.email, REST_PASSWORD_TYPE) { code ->
            // 验证码错误
            if (code?.uppercase() != restPassword.verificationCode.uppercase()) {
                throw ApiException(ApiStatus.U_2005)
            }
            // 当前旧密码不能与当前密码一致
            if (!passwordEncoder.matches(restPassword.newPassword, user.password)) {
                throw ApiException(ApiStatus.U_2008)
            }
            // 设置更新密码时间 用于失效旧TOKEN
            user.passwordLastModifiedDate = Date()
            user.password = passwordEncoder.encode(restPassword.newPassword)
            userRepository.save(user)
        }

    }


}